package com.fansl.allround.daemon.quartz.util;

import com.fansl.allround.common.core.constant.enums.ScheduleJobStatusEnum;
import com.fansl.allround.common.core.constant.enums.ScheduleMisfirePolicyEnum;
import com.fansl.allround.daemon.quartz.config.ScheduleConstants;
import com.fansl.allround.daemon.quartz.entity.ScheduleJob;
import com.fansl.allround.daemon.quartz.jobs.QuartzDisallowConcurrentExecutionJob;
import com.fansl.allround.daemon.quartz.jobs.QuartzJob;
import com.fansl.allround.daemon.quartz.listener.DefaultGlobalJobListener;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.KeyMatcher;

/**
 * @author fansl
 * @Description: TODO
 * @date 2020/1/10 17:02
 */
@Slf4j
public class ScheduleJobUtil {

    /**
     * 获取触发器KEY
     *
     * @param jobId
     * @param jobGroup
     * @return
     */
    public static TriggerKey getTriggerKey(Long jobId,String jobGroup) {
        return TriggerKey.triggerKey(ScheduleConstants.TRIGGER_NAME_PREFIX + jobId,jobGroup);
    }

    /**
     * 获取JOB key
     * @param jobId
     * @param jobGroup
     * @return
     */
    public static JobKey getJobKey(Long jobId,String jobGroup) {
        return JobKey.jobKey(ScheduleConstants.JOB_NAME_PREFIX + jobId,jobGroup);
    }

    /**
     * 获取表达式触发器
     *
     * @param scheduler
     * @param jobId
     * @param jobGroup
     * @return
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId,String jobGroup) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId,jobGroup));
        } catch (SchedulerException e) {
            throw new RuntimeException("获取定时任务CronTrigger出现异常", e);
        }
    }

    public static void createScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            Class<? extends Job> jobClass = getQuartzJobClass(scheduleJob);
            Long jobId = scheduleJob.getJobId();
            String jobGroup = scheduleJob.getJobGroup();
            //构建job信息
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(getJobKey(jobId,jobGroup))
                    .build();

            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());
            scheduleBuilder = handleCronScheduleMisfirePolicy(scheduleJob,scheduleBuilder);

            TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId(),jobGroup);
            //按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerKey)
                    .withSchedule(scheduleBuilder)
                    .withDescription(scheduleJob.getRemark())
                    .build();

            //放入参数，运行时的方法可以获取
            jobDetail.getJobDataMap()
                    .put(ScheduleConstants.JOB_PROPERTIES_KEY, scheduleJob);

            JobKey jobKey = getJobKey(jobId,scheduleJob.getJobGroup());
            //是否已经存在
            if(scheduler.checkExists(jobKey)){
                scheduler.deleteJob(jobKey);
            }
            scheduler.getListenerManager()
                    .addJobListener(new DefaultGlobalJobListener(),KeyMatcher.keyEquals(jobKey));
            scheduler.scheduleJob(jobDetail, trigger);
            //暂停任务
            if (scheduleJob.getStatus() == ScheduleJobStatusEnum.PAUSE.getCode()) {
                pauseJob(scheduler, jobId,jobGroup);
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("创建定时任务失败", e);
        }
    }

    /**
     * 更新定时任务
     *
     * @param scheduler
     * @param scheduleJob
     */
    public static void updateScheduleJob(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            TriggerKey triggerKey = getTriggerKey(scheduleJob.getJobId(),scheduleJob.getJobGroup());
            //表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());
            scheduleBuilder = handleCronScheduleMisfirePolicy(scheduleJob,scheduleBuilder);

            CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getJobId(),scheduleJob.getJobGroup());
            //按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(scheduleBuilder)
                    .build();

            //参数
            trigger.getJobDataMap().put(ScheduleConstants.JOB_PROPERTIES_KEY, scheduleJob);
            JobKey jobKey = getJobKey(scheduleJob.getJobId(),scheduleJob.getJobGroup());
            scheduler.getListenerManager()
                    .addJobListener(new DefaultGlobalJobListener(),KeyMatcher.keyEquals(jobKey));
            scheduler.rescheduleJob(triggerKey, trigger);
            //暂停任务
            if (scheduleJob.getStatus() == ScheduleJobStatusEnum.PAUSE.getCode()) {
                pauseJob(scheduler, scheduleJob.getJobId(),scheduleJob.getJobGroup());
            }
        } catch (SchedulerException e) {
            throw new RuntimeException("更新定时任务失败", e);
        }
    }

    /**
     * 执行任务
     *
     * @param scheduler
     * @param scheduleJob
     */
    public static void run(Scheduler scheduler, ScheduleJob scheduleJob) {
        try {
            JobDataMap dataMap = new JobDataMap();
            dataMap.put(ScheduleConstants.JOB_PROPERTIES_KEY, scheduleJob);

            scheduler.triggerJob(getJobKey(scheduleJob.getJobId(),scheduleJob.getJobGroup()), dataMap);
        } catch (SchedulerException e) {
            throw new RuntimeException("立即执行定时任务失败", e);
        }
    }

    /**
     * 暂停任务
     *
     * @param scheduler
     * @param jobId
     */
    public static void pauseJob(Scheduler scheduler, Long jobId,String jobGroup) {
        try {
            scheduler.pauseJob(getJobKey(jobId,jobGroup));
        } catch (SchedulerException e) {
            throw new RuntimeException("暂停定时任务失败", e);
        }
    }

    /**
     * 恢复任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroup
     */
    public static void resumeJob(Scheduler scheduler,Long jobId,String jobGroup) {
        try {
            scheduler.resumeJob(getJobKey(jobId,jobGroup));
        } catch (SchedulerException e) {
            throw new RuntimeException("恢复定时任务失败", e);
        }
    }

    /**
     * 删除定时任务
     *
     * @param scheduler
     * @param jobId
     * @param jobGroup
     */
    public static void deleteSchedulerJob(Scheduler scheduler, Long jobId,String jobGroup) {
        try {
            //停止触发器
            scheduler.pauseTrigger(getTriggerKey(jobId,jobGroup));
            //移除触发器
            scheduler.unscheduleJob(getTriggerKey(jobId,jobGroup));
            //删除任务
            scheduler.deleteJob(getJobKey(jobId,jobGroup));
        } catch (SchedulerException e) {
            throw new RuntimeException("删除定时任务失败", e);
        }
    }

    /**
     * 任务出错后的执行策略
     * @param job
     * @param cb
     * @return
     */
    private static CronScheduleBuilder handleCronScheduleMisfirePolicy(ScheduleJob job, CronScheduleBuilder cb) {
        switch (ScheduleMisfirePolicyEnum.getByCode(job.getMisfirePolicy())) {
            case MISFIRE_IGNORE_MISFIRES:
                //所有misfire的任务会马上执行
                //打个比方，如果9点misfire了，在10：15系统恢复之后，9点，10点的misfire会马上执行
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case MISFIRE_FIRE_AND_PROCEED:
                //会合并部分的misfire,正常执行下一个周期的任务
                //假设9，10的任务都misfire了，系统在10：15分起来了。只会执行一次misfire，下次正点执行。
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case MISFIRE_DO_NOTHING:
                //所有的misfire不管，执行下一个周期的任务
                return cb.withMisfireHandlingInstructionDoNothing();
            case MISFIRE_DEFAULT:
                return cb;
            default:
                return cb;
        }
    }

    /**
     * 得到quartz任务类
     *
     * @param scheduleJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(ScheduleJob scheduleJob) {
        boolean isConcurrent = "0".equals(scheduleJob.getConcurrent());
        return isConcurrent ? QuartzJob.class : QuartzDisallowConcurrentExecutionJob.class;
    }
}
